﻿/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
 *
 * == BEGIN LICENSE ==
 *
 * Licensed under the terms of any of the following licenses at your
 * choice:
 *
 *  - GNU General Public License Version 2 or later (the "GPL")
 *    http://www.gnu.org/licenses/gpl.html
 *
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 *
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 *
 * == END LICENSE ==
 *
 * FCKEditingArea Class: renders an editable area.
 */

/**
 * @constructor
 * @param {String} targetElement The element that will hold the editing area. Any child element present in the target will be deleted.
 */
var FCKEditingArea = function( targetElement )
{
    this.TargetElement = targetElement ;
    this.Mode = FCK_EDITMODE_WYSIWYG ;

    if ( FCK.IECleanup )
        FCK.IECleanup.AddItem( this, FCKEditingArea_Cleanup ) ;
}


/**
 * @param {String} html The complete HTML for the page, including DOCTYPE and the <html> tag.
 */
FCKEditingArea.prototype.Start = function( html, secondCall )
{
    var eTargetElement    = this.TargetElement ;
    var oTargetDocument    = FCKTools.GetElementDocument( eTargetElement ) ;

    // Remove all child nodes from the target.
    while( eTargetElement.firstChild )
        eTargetElement.removeChild( eTargetElement.firstChild ) ;

    if ( this.Mode == FCK_EDITMODE_WYSIWYG )
    {
        // For FF, document.domain must be set only when different, otherwhise
        // we'll strangely have "Permission denied" issues.
        if ( FCK_IS_CUSTOM_DOMAIN )
            html = '<script>document.domain="' + FCK_RUNTIME_DOMAIN + '";</script>' + html ;

        // IE has a bug with the <base> tag... it must have a </base> closer,
        // otherwise the all successive tags will be set as children nodes of the <base>.
        if ( FCKBrowserInfo.IsIE )
            html = html.replace( /(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi, '$1></base>' ) ;
        else if ( !secondCall )
        {
            // Gecko moves some tags out of the body to the head, so we must use
            // innerHTML to set the body contents (SF BUG 1526154).

            // Extract the BODY contents from the html.
            var oMatchBefore = html.match( FCKRegexLib.BeforeBody ) ;
            var oMatchAfter = html.match( FCKRegexLib.AfterBody ) ;

            if ( oMatchBefore && oMatchAfter )
            {
                var sBody = html.substr( oMatchBefore[1].length,
                           html.length - oMatchBefore[1].length - oMatchAfter[1].length ) ;    // This is the BODY tag contents.

                html =
                    oMatchBefore[1] +            // This is the HTML until the <body...> tag, inclusive.
                    '&nbsp;' +
                    oMatchAfter[1] ;            // This is the HTML from the </body> tag, inclusive.

                // If nothing in the body, place a BOGUS tag so the cursor will appear.
                if ( FCKBrowserInfo.IsGecko && ( sBody.length == 0 || FCKRegexLib.EmptyParagraph.test( sBody ) ) )
                    sBody = '<br type="_moz">' ;

                this._BodyHTML = sBody ;

            }
            else
                this._BodyHTML = html ;            // Invalid HTML input.
        }

        // Create the editing area IFRAME.
        var oIFrame = this.IFrame = oTargetDocument.createElement( 'iframe' ) ;

        // IE: Avoid JavaScript errors thrown by the editing are source (like tags events).
        // See #1055.
        var sOverrideError = '<script type="text/javascript" _fcktemp="true">window.onerror=function(){return true;};</script>' ;

        oIFrame.frameBorder = 0 ;
        oIFrame.style.width = oIFrame.style.height = '100%' ;

        if ( FCK_IS_CUSTOM_DOMAIN && FCKBrowserInfo.IsIE )
        {
            window._FCKHtmlToLoad = html.replace( /<head>/i, '<head>' + sOverrideError ) ;
            oIFrame.src = 'javascript:void( (function(){' +
                'document.open() ;' +
                'document.domain="' + document.domain + '" ;' +
                'document.write( window.parent._FCKHtmlToLoad );' +
                'document.close() ;' +
                'window.parent._FCKHtmlToLoad = null ;' +
                '})() )' ;
        }
        else if ( !FCKBrowserInfo.IsGecko )
        {
            // Firefox will render the tables inside the body in Quirks mode if the
            // source of the iframe is set to javascript. see #515
            oIFrame.src = 'javascript:void(0)' ;
        }

        // Append the new IFRAME to the target. For IE, it must be done after
        // setting the "src", to avoid the "secure/unsecure" message under HTTPS.
        eTargetElement.appendChild( oIFrame ) ;

        // Get the window and document objects used to interact with the newly created IFRAME.
        this.Window = oIFrame.contentWindow ;

        // IE: Avoid JavaScript errors thrown by the editing are source (like tags events).
        // TODO: This error handler is not being fired.
        // this.Window.onerror = function() { alert( 'Error!' ) ; return true ; }

        if ( !FCK_IS_CUSTOM_DOMAIN || !FCKBrowserInfo.IsIE )
        {
            var oDoc = this.Window.document ;

            oDoc.open() ;
            oDoc.write( html.replace( /<head>/i, '<head>' + sOverrideError ) ) ;
            oDoc.close() ;
        }

        if ( FCKBrowserInfo.IsAIR )
            FCKAdobeAIR.EditingArea_Start( oDoc, html ) ;

        // Firefox 1.0.x is buggy... ohh yes... so let's do it two times and it
        // will magically work.
        if ( FCKBrowserInfo.IsGecko10 && !secondCall )
        {
            this.Start( html, true ) ;
            return ;
        }

        if ( oIFrame.readyState && oIFrame.readyState != 'completed' )
        {
            var editArea = this ;

            // Using a IE alternative for DOMContentLoaded, similar to the
            // solution proposed at http://javascript.nwbox.com/IEContentLoaded/
            setTimeout( function()
                    {
                        try
                        {
                            editArea.Window.document.documentElement.doScroll("left") ;
                        }
                        catch(e)
                        {
                            setTimeout( arguments.callee, 0 ) ;
                            return ;
                        }
                        editArea.Window._FCKEditingArea = editArea ;
                        FCKEditingArea_CompleteStart.call( editArea.Window ) ;
                    }, 0 ) ;
        }
        else
        {
            this.Window._FCKEditingArea = this ;

            // FF 1.0.x is buggy... we must wait a lot to enable editing because
            // sometimes the content simply disappears, for example when pasting
            // "bla1!<img src='some_url'>!bla2" in the source and then switching
            // back to design.
            if ( FCKBrowserInfo.IsGecko10 )
                this.Window.setTimeout( FCKEditingArea_CompleteStart, 500 ) ;
            else
                FCKEditingArea_CompleteStart.call( this.Window ) ;
        }
    }
    else
    {
        var eTextarea = this.Textarea = oTargetDocument.createElement( 'textarea' ) ;
        eTextarea.className = 'SourceField' ;
        eTextarea.dir = 'ltr' ;
        FCKDomTools.SetElementStyles( eTextarea,
            {
                width    : '100%',
                height    : '100%',
                border    : 'none',
                resize    : 'none',
                outline    : 'none'
            } ) ;
        eTargetElement.appendChild( eTextarea ) ;

        eTextarea.value = html  ;

        // Fire the "OnLoad" event.
        FCKTools.RunFunction( this.OnLoad ) ;
    }
}

// "this" here is FCKEditingArea.Window
function FCKEditingArea_CompleteStart()
{
    // On Firefox, the DOM takes a little to become available. So we must wait for it in a loop.
    if ( !this.document.body )
    {
        this.setTimeout( FCKEditingArea_CompleteStart, 50 ) ;
        return ;
    }

    var oEditorArea = this._FCKEditingArea ;

    // Save this reference to be re-used later.
    oEditorArea.Document = oEditorArea.Window.document ;

    oEditorArea.MakeEditable() ;

    // Fire the "OnLoad" event.
    FCKTools.RunFunction( oEditorArea.OnLoad ) ;
}

FCKEditingArea.prototype.MakeEditable = function()
{
    var oDoc = this.Document ;

    if ( FCKBrowserInfo.IsIE )
    {
        // Kludge for #141 and #523
        oDoc.body.disabled = true ;
        oDoc.body.contentEditable = true ;
        oDoc.body.removeAttribute( "disabled" ) ;

        /* The following commands don't throw errors, but have no effect.
        oDoc.execCommand( 'AutoDetect', false, false ) ;
        oDoc.execCommand( 'KeepSelection', false, true ) ;
        */
    }
    else
    {
        try
        {
            // Disable Firefox 2 Spell Checker.
            oDoc.body.spellcheck = ( this.FFSpellChecker !== false ) ;

            if ( this._BodyHTML )
            {
                oDoc.body.innerHTML = this._BodyHTML ;
                oDoc.body.offsetLeft ;        // Don't remove, this is a hack to fix Opera 9.50, see #2264.
                this._BodyHTML = null ;
            }

            oDoc.designMode = 'on' ;

            // Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects (by Alfonso Martinez)
            oDoc.execCommand( 'enableObjectResizing', false, !FCKConfig.DisableObjectResizing ) ;

            // Disable the standard table editing features of Firefox.
            oDoc.execCommand( 'enableInlineTableEditing', false, !FCKConfig.DisableFFTableHandles ) ;
        }
        catch (e)
        {
            // In Firefox if the iframe is initially hidden it can't be set to designMode and it raises an exception
            // So we set up a DOM Mutation event Listener on the HTML, as it will raise several events when the document is  visible again
            FCKTools.AddEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ;
        }

    }
}

// This function processes the notifications of the DOM Mutation event on the document
// We use it to know that the document will be ready to be editable again (or we hope so)
function FCKEditingArea_Document_AttributeNodeModified( evt )
{
    var editingArea = evt.currentTarget.contentWindow._FCKEditingArea ;

    // We want to run our function after the events no longer fire, so we can know that it's a stable situation
    if ( editingArea._timer )
        window.clearTimeout( editingArea._timer ) ;

    editingArea._timer = FCKTools.SetTimeout( FCKEditingArea_MakeEditableByMutation, 1000, editingArea ) ;
}

// This function ideally should be called after the document is visible, it does clean up of the
// mutation tracking and tries again to make the area editable.
function FCKEditingArea_MakeEditableByMutation()
{
    // Clean up
    delete this._timer ;
    // Now we don't want to keep on getting this event
    FCKTools.RemoveEventListener( this.Window.frameElement, 'DOMAttrModified', FCKEditingArea_Document_AttributeNodeModified ) ;
    // Let's try now to set the editing area editable
    // If it fails it will set up the Mutation Listener again automatically
    this.MakeEditable() ;
}

FCKEditingArea.prototype.Focus = function()
{
    try
    {
        if ( this.Mode == FCK_EDITMODE_WYSIWYG )
        {
            if ( FCKBrowserInfo.IsIE )
                this._FocusIE() ;
            else
                this.Window.focus() ;
        }
        else
        {
            var oDoc = FCKTools.GetElementDocument( this.Textarea ) ;
            if ( (!oDoc.hasFocus || oDoc.hasFocus() ) && oDoc.activeElement == this.Textarea )
                return ;

            this.Textarea.focus() ;
        }
    }
    catch(e) {}
}

FCKEditingArea.prototype._FocusIE = function()
{
    // In IE it can happen that the document is in theory focused but the
    // active element is outside of it.
    this.Document.body.setActive() ;

    this.Window.focus() ;

    // Kludge for #141... yet more code to workaround IE bugs
    var range = this.Document.selection.createRange() ;

    var parentNode = range.parentElement() ;
    var parentTag = parentNode.nodeName.toLowerCase() ;

    // Only apply the fix when in a block, and the block is empty.
    if ( parentNode.childNodes.length > 0 ||
         !( FCKListsLib.BlockElements[parentTag] ||
            FCKListsLib.NonEmptyBlockElements[parentTag] ) )
    {
        return ;
    }

    // Force the selection to happen, in this way we guarantee the focus will
    // be there.
    range = new FCKDomRange( this.Window ) ;
    range.MoveToElementEditStart( parentNode ) ;
    range.Select() ;
}

function FCKEditingArea_Cleanup()
{
    if ( this.Document )
        this.Document.body.innerHTML = "" ;
    this.TargetElement = null ;
    this.IFrame = null ;
    this.Document = null ;
    this.Textarea = null ;

    if ( this.Window )
    {
        this.Window._FCKEditingArea = null ;
        this.Window = null ;
    }
}
